/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.persistence.dao;

import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.telosys.common.TelosysRuntimeException;
import org.telosys.persistence.mappers.FieldMapperBigDecimal;
import org.telosys.persistence.mappers.FieldMapperBlob;
import org.telosys.persistence.mappers.FieldMapperBooleanStoredAsInt;
import org.telosys.persistence.mappers.FieldMapperBooleanStoredAsString;
import org.telosys.persistence.mappers.FieldMapperByte;
import org.telosys.persistence.mappers.FieldMapperByteArray;
import org.telosys.persistence.mappers.FieldMapperByteArrayStoredAsBlob;
import org.telosys.persistence.mappers.FieldMapperCharArrayStoredAsClob;
import org.telosys.persistence.mappers.FieldMapperClob;
import org.telosys.persistence.mappers.FieldMapperDateSql;
import org.telosys.persistence.mappers.FieldMapperDateUtil;
import org.telosys.persistence.mappers.FieldMapperDouble;
import org.telosys.persistence.mappers.FieldMapperFloat;
import org.telosys.persistence.mappers.FieldMapperInteger;
import org.telosys.persistence.mappers.FieldMapperLong;
import org.telosys.persistence.mappers.FieldMapperPrimitiveBoolean;
import org.telosys.persistence.mappers.FieldMapperPrimitiveBooleanStoredAsInt;
import org.telosys.persistence.mappers.FieldMapperPrimitiveBooleanStoredAsString;
import org.telosys.persistence.mappers.FieldMapperPrimitiveByte;
import org.telosys.persistence.mappers.FieldMapperPrimitiveDouble;
import org.telosys.persistence.mappers.FieldMapperPrimitiveFloat;
import org.telosys.persistence.mappers.FieldMapperPrimitiveInt;
import org.telosys.persistence.mappers.FieldMapperPrimitiveLong;
import org.telosys.persistence.mappers.FieldMapperPrimitiveShort;
import org.telosys.persistence.mappers.FieldMapperShort;
import org.telosys.persistence.mappers.FieldMapperString;
import org.telosys.persistence.mappers.FieldMapperTime;
import org.telosys.persistence.mappers.FieldMapperTimestamp;
import org.telosys.persistence.mapping.BooleanMappedToInt;
import org.telosys.persistence.mapping.BooleanMappedToString;
import org.telosys.persistence.mapping.ByteArrayStorageType;
import org.telosys.persistence.mapping.DateType;

public class SpecializedFieldMapperProvider {

	/**
	 * This map contains all the standard mappers for each type of field 
	 */
	private final static Map<Class<?>, SpecializedFieldMapper> map = createMap() ;
	
	private static Map<Class<?>, SpecializedFieldMapper> createMap() 
	{
		HashMap<Class<?>, SpecializedFieldMapper> hashMap = new HashMap<Class<?>, SpecializedFieldMapper>();

		//--- Primitive Numeric types 
        hashMap.put(byte.class,   new FieldMapperPrimitiveByte() );
        hashMap.put(short.class,  new FieldMapperPrimitiveShort() );
        hashMap.put(int.class,    new FieldMapperPrimitiveInt() );
        hashMap.put(long.class,   new FieldMapperPrimitiveLong() );
        hashMap.put(float.class,  new FieldMapperPrimitiveFloat() );
        hashMap.put(double.class, new FieldMapperPrimitiveDouble() );
        
        //--- Objects numeric wrappers
        hashMap.put(Byte.class,       new FieldMapperByte() );
        hashMap.put(Short.class,      new FieldMapperShort() );
        hashMap.put(Integer.class,    new FieldMapperInteger() );
        hashMap.put(Long.class,       new FieldMapperLong() );
        hashMap.put(Float.class,      new FieldMapperFloat() );
        hashMap.put(Double.class,     new FieldMapperDouble() );
        
        hashMap.put(BigDecimal.class, new FieldMapperBigDecimal() );

        hashMap.put(String.class,     new FieldMapperString() );

        //--- Date and time 
        hashMap.put(java.sql.Date.class,      new FieldMapperDateSql() );
        hashMap.put(java.sql.Time.class,      new FieldMapperTime() );
        hashMap.put(java.sql.Timestamp.class, new FieldMapperTimestamp() );
        hashMap.put(java.util.Date.class,     new FieldMapperDateUtil(DateType.dateOnly) ); // Default mapper if no further information
        
        //--- Boolean (by default considered stored as boolean in the database )
        hashMap.put(boolean.class,      new FieldMapperPrimitiveBoolean() ); // Default boolean mapper (if no further information) 
        hashMap.put(Boolean.class,      new FieldMapperPrimitiveBoolean() ); // Default boolean mapper (if no further information) 
        
        //--- Binary objects
        hashMap.put(byte[].class,       new FieldMapperByteArray() ); 
        hashMap.put(Blob.class,         new FieldMapperBlob() ); 

        //--- Character Large Objects
        hashMap.put(char[].class,       new FieldMapperCharArrayStoredAsClob() ); 
        hashMap.put(Clob.class,         new FieldMapperClob() ); 
        
        return Collections.unmodifiableMap(hashMap);
    }

	//--- Special field mappers ( thread safe => mono instance )
	private final static FieldMapperByteArray             mapperByteArray             = new FieldMapperByteArray(); 
	private final static FieldMapperByteArrayStoredAsBlob mapperByteArrayStoredAsBLOB = new FieldMapperByteArrayStoredAsBlob(); 
//	private final static FieldMapperByteArrayStoredAsClob mapperByteArrayStoredAsCLOB = new FieldMapperByteArrayStoredAsClob(); 
//	private final static FieldMapperCharArrayStoredAsClob mapperCharArrayStoredAsCLOB = new FieldMapperCharArrayStoredAsClob(); 
	
	
	public static SpecializedFieldMapper getMapperForType(Class<?> type, Object furtherInformation ) 
	{
		if ( furtherInformation != null ) 
		{
			if ( "java.util.Date".equals( type.getCanonicalName() ) ) 
			{
				if ( furtherInformation instanceof DateType ) {
					return new FieldMapperDateUtil( (DateType) furtherInformation );
				}
				else {
					throw new TelosysRuntimeException("Invalid further information in mapping for 'Date' type ");
				}
			}
			else if ( "boolean".equals( type.getCanonicalName() ) ) 
			{
				if ( furtherInformation instanceof BooleanMappedToInt ) 
				{
					BooleanMappedToInt info = (BooleanMappedToInt) furtherInformation ;
					return new FieldMapperPrimitiveBooleanStoredAsInt( info.getTrueValue(), info.getFalseValue() );
				}
				else if ( furtherInformation instanceof BooleanMappedToString ) 
				{
					BooleanMappedToString info = (BooleanMappedToString) furtherInformation ;
					return new FieldMapperPrimitiveBooleanStoredAsString( info.getTrueValue(), info.getFalseValue() );
				}
				else 
				{
					throw new TelosysRuntimeException("Invalid further information in mapping for 'boolean' type ");
				}
			}
			else if ( "java.lang.Boolean".equals( type.getCanonicalName() ) ) 
			{
				if ( furtherInformation instanceof BooleanMappedToInt ) 
				{
					BooleanMappedToInt info = (BooleanMappedToInt) furtherInformation ;
					return new FieldMapperBooleanStoredAsInt( info.getTrueValue(), info.getFalseValue() );
				}
				else if ( furtherInformation instanceof BooleanMappedToString ) 
				{
					BooleanMappedToString info = (BooleanMappedToString) furtherInformation ;
					return new FieldMapperBooleanStoredAsString( info.getTrueValue(), info.getFalseValue() );
				}
				else 
				{
					throw new TelosysRuntimeException("Invalid further information in mapping for 'Boolean' type ");
				}
			}
			else if ( "byte[]".equals( type.getCanonicalName() ) ) 
			{
				if ( furtherInformation instanceof ByteArrayStorageType )
				{
					ByteArrayStorageType storedAs = (ByteArrayStorageType) furtherInformation ;
					if ( storedAs == ByteArrayStorageType.BYTE_ARRAY )  
					{
						return mapperByteArray ; // Standard storage
					}
					else if ( storedAs == ByteArrayStorageType.BLOB )  
					{
						return mapperByteArrayStoredAsBLOB ;
					}
//					else if ( storedAs == ByteArrayStorageType.CLOB )  
//					{
//						return mapperByteArrayStoredAsCLOB ;
//					}
					else {
						throw new TelosysRuntimeException("Invalid further information in mapping for 'byte[]' type ");
					}
				}
//				if ( furtherInformation instanceof BytesStorage ) 
//				{
//					BytesStorage binaryStorage = (BytesStorage) furtherInformation ;
//					if ( binaryStorage.isBlob() )
//					{
//						return new FieldMapperArrayOfBytesStoredAsBlob(); // Storage in a BLOB column  
//					}
//					else
//					{
//						return new FieldMapperArrayOfBytes(); // Standard storage 
//					}
//				}
				else 
				{
					throw new TelosysRuntimeException("Invalid further information in mapping for 'byte[]' type ");
				}
			}
			else 
			{
				//--- useless further information for this kind of field => use standard mapper
				return map.get(type);
			}
		}
		else 
		{
			//--- no specific further information => reuse existing mapper
			return map.get(type);
		}
	}
}
